#region  Apache  License
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion

// Compatibility:
//   http://msdn.microsoft.com/en-us/library/system.console.foregroundcolor.aspx

// The original ColoredConsoleAppender was written before the .NET framework
// (and Mono) had built-in support for console colors so it was written using
// Win32 API calls. The AnsiColorTerminalAppender, while it works, isn't
// understood by the Windows command prompt.
// This is a replacement for both that uses the new Console colors
// and works on both platforms.

// On Mono/Linux (at least), setting the background color to 'Black' is
// not the same as the default background color, as it is after
// Console.Reset(). The difference becomes apparent while running in a
// terminal application that supports background transparency; the
// default color is treated as transparent while 'Black' isn't.
// For this reason, we always reset the colors and only set those
// explicitly specified in the configuration (Console.BackgroundColor
// isn't set if omitted).

using System;
using System.IO;
using log4net.Core;
using log4net.Util;

namespace log4net.Appender;

/// <summary>
/// Appends colorful logging events to the console, using .NET built-in capabilities.
/// </summary>
/// <remarks>
/// <para>
/// ManagedColoredConsoleAppender appends log events to the standard output stream
/// or the error output stream using a layout specified by the
/// user. It also allows the color of a specific type of message to be set.
/// </para>
/// <para>
/// By default, all output is written to the console's standard output stream.
/// The <see cref="Target"/> property can be set to direct the output to the
/// error stream.
/// </para>
/// <para>
/// When configuring the colored console appender, mappings should be
/// specified to map logging levels to colors. For example:
/// </para>
/// <code lang="XML" escaped="true">
/// <mapping>
///  <level value="ERROR" />
///  <foreColor value="DarkRed" />
///  <backColor value="White" />
/// </mapping>
/// <mapping>
///  <level value="WARN" />
///  <foreColor value="Yellow" />
/// </mapping>
/// <mapping>
///  <level value="INFO" />
///  <foreColor value="White" />
/// </mapping>
/// <mapping>
///  <level value="DEBUG" />
///  <foreColor value="Blue" />
/// </mapping>
/// </code>
/// <para>
/// The Level is the standard log4net logging level while
/// ForeColor and BackColor are the values of <see cref="ConsoleColor"/>
/// enumeration.
/// </para>
/// <para>
/// Based on the ColoredConsoleAppender
/// </para>
/// </remarks>
/// <author>Rick Hobbs</author>
/// <author>Nicko Cadell</author>
/// <author>Pavlos Touboulidis</author>
public class ManagedColoredConsoleAppender : AppenderSkeleton
{
  /// <summary>
  /// Gets or sets the console output stream.
  /// This is either <c>"Console.Out"</c> or <c>"Console.Error"</c>.
  /// </summary>
  public virtual string Target
  {
    get => _writeToErrorStream ? ConsoleError : ConsoleOut;
    set => _writeToErrorStream = SystemInfo.EqualsIgnoringCase(ConsoleError, value?.Trim());
  }

  /// <summary>
  /// Add a mapping of level to color - done by the config file
  /// </summary>
  /// <param name="mapping">The mapping to add</param>
  /// <remarks>
  /// <para>
  /// Each mapping defines the foreground and background colors
  /// for a level.
  /// </para>
  /// </remarks>
  public void AddMapping(LevelColors mapping) => _levelMapping.Add(mapping);

  /// <summary>
  /// Writes the event to the console.
  /// </summary>
  /// <param name="loggingEvent">The event to log.</param>
  /// <remarks>
  /// <para>
  /// This method is called by the <see cref="AppenderSkeleton.DoAppend(log4net.Core.LoggingEvent)"/> method.
  /// </para>
  /// <para>
  /// The format of the output will depend on the appender's layout.
  /// </para>
  /// </remarks>
  protected override void Append(LoggingEvent loggingEvent)
  {
    TextWriter writer = _writeToErrorStream ? Console.Error : Console.Out;

    Console.ResetColor();

    // See if there is a specified lookup
    if (_levelMapping.Lookup(loggingEvent.EnsureNotNull().Level) is LevelColors levelColors)
    {
      // If the backColor has been explicitly set
      if (levelColors.HasBackColor)
      {
        Console.BackgroundColor = levelColors.BackColor;
      }

      // If the foreColor has been explicitly set
      if (levelColors.HasForeColor)
      {
        Console.ForegroundColor = levelColors.ForeColor;
      }
    }

    // Render the event to a string
    string strLoggingMessage = RenderLoggingEvent(loggingEvent);
    // and write it
    writer.Write(strLoggingMessage);

    // Reset color again
    Console.ResetColor();
  }

  /// <summary>
  /// This appender requires a <see cref="Layout"/> to be set.
  /// </summary>
  protected override bool RequiresLayout => true;

  /// <summary>
  /// Initializes the options for this appender.
  /// </summary>
  public override void ActivateOptions()
  {
    base.ActivateOptions();
    _levelMapping.ActivateOptions();
  }

  /// <summary>
  /// The <see cref="Target"/> to use when writing to the Console
  /// standard output stream.
  /// </summary>
  public const string ConsoleOut = "Console.Out";

  /// <summary>
  /// The <see cref="Target"/> to use when writing to the Console
  /// standard error output stream.
  /// </summary>
  public const string ConsoleError = "Console.Error";

  /// <summary>
  /// Flag to write output to the error stream rather than the standard output stream
  /// </summary>
  private bool _writeToErrorStream;

  /// <summary>
  /// Mapping from level object to color value
  /// </summary>
  private readonly LevelMapping _levelMapping = new();

  /// <summary>
  /// A class to act as a mapping between the level that a logging call is made at and
  /// the color it should be displayed as.
  /// </summary>
  public class LevelColors : LevelMappingEntry
  {
    /// <summary>
    /// The mapped foreground color for the specified level
    /// </summary>
    public ConsoleColor ForeColor
    {
      get => _foreColor;
      // Keep a flag that the color has been set
      // and is no longer the default.
      set { _foreColor = value; HasForeColor = true; }
    }
    private ConsoleColor _foreColor;

    internal bool HasForeColor { get; private set; }

    /// <summary>
    /// Gets or sets the mapped background color for the specified level
    /// </summary>
    public ConsoleColor BackColor
    {
      get => _backColor;
      // Keep a flag that the color has been set
      // and is no longer the default.
      set { _backColor = value; HasBackColor = true; }
    }
    private ConsoleColor _backColor;

    internal bool HasBackColor { get; private set; }
  }
}
